package my.mark.mybaibaoxiang.system.kit;

import my.mark.mybaibaoxiang.platformBase.util.OSinfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 
 * @Description: class扫描器
 * @Author wxming
 * @Date 2014年9月19日 下午2:06:18
 */
public class ClassSearcher {

    private final Logger log = LoggerFactory.getLogger(ClassSearcher.class);

    private List<String> includeJars = new ArrayList<String>();
    private List<String> includePackage = new ArrayList<String>();

    private List<Class<?>> extraction(Class<?> clazz, List<String> classFileList,
                                      List<String> includePackage) {
        List<Class<?>> classList = new ArrayList<Class<?>>();
        for (String classFile : classFileList) {
            if (checkPackage(classFile, includePackage)) {
                try {
                    Class<?> classInFile = Class.forName(classFile);
                    if (clazz.isAssignableFrom(classInFile) && clazz != classInFile) {
                        classList.add(classInFile);
                    }
                } catch (ClassNotFoundException e) {
                    log.error("class [" + classFile + "] instance failed !", e);
                }
            }
        }

        return classList;
    }

    private boolean checkPackage(String classFile, List<String> includePackage) {
        if (includePackage.size() == 0)
            return true;
        for (String p : includePackage) {
            Pattern pattern = Pattern.compile(p.replaceAll("\\.", "").replaceAll("\\*", ".*")
                                              + ".*");
            Matcher matcher = pattern.matcher(classFile.replaceAll("\\.", ""));
            if (matcher.matches())
                return true;
        }
        return false;
    }

    public static ClassSearcher of(Class<?> target) {
        return new ClassSearcher(target);
    }

    /**
     * 递归查找文件
     * @param rootDirName
     *            查找的文件夹根路径
     * @param baseDirName
     *            查找的文件夹路径
     * @param targetFileName
     *            需要查找的文件名
     */
    private List<String> findFiles(String rootDirName, String baseDirName, String targetFileName) {
        /**
         * 算法简述： 从某个给定的需查找的文件夹出发，搜索该文件夹的所有子文件夹及文件， 若为文件，则进行匹配，匹配成功则加入结果集，若为子文件夹，则进队列。 队列不空，重复上述操作，队列为空，程序结束，返回结果。
         */
        List<String> classFiles = new ArrayList<String>();
        String tempName = null;
        // 判断目录是否存在
        File baseDir = new File(baseDirName);
        if (!baseDir.exists() || !baseDir.isDirectory()) {
            log.error("search error：" + baseDirName + "is not a dir！");
        } else {
            String[] filelist = baseDir.list();
            for (int i = 0; i < filelist.length; i++) {
                File readfile = new File(baseDirName + File.separator + filelist[i]);
                if (readfile.isDirectory()) {
                    classFiles.addAll(findFiles(rootDirName, baseDirName + File.separator
                                                             + filelist[i], targetFileName));
                } else {
                    tempName = readfile.getName();
                    if (ClassSearcher.wildcardMatch(targetFileName, tempName)) {
                        String classname = readfile.getAbsolutePath().replaceAll("\\\\", "/");
                        classname = classname.substring(rootDirName.replaceAll("\\\\", "/")
                            .length() + 1, classname.length() - 6);
                        classFiles.add(classname.replaceAll("/", "."));
                    }
                }
            }
        }
        return classFiles;
    }

    /**
     * 通配符匹配
     * 
     * @param pattern
     *            通配符模式
     * @param str
     *            待匹配的字符串
     *            匹配成功则返回true，否则返回false
     */
    private static boolean wildcardMatch(String pattern, String str) {
        int patternLength = pattern.length();
        int strLength = str.length();
        int strIndex = 0;
        char ch;
        for (int patternIndex = 0; patternIndex < patternLength; patternIndex++) {
            ch = pattern.charAt(patternIndex);
            if (ch == '*') {
                // 通配符星号*表示可以匹配任意多个字符
                while (strIndex < strLength) {
                    if (wildcardMatch(pattern.substring(patternIndex + 1), str.substring(strIndex))) {
                        return true;
                    }
                    strIndex++;
                }
            } else if (ch == '?') {
                // 通配符问号?表示匹配任意一个字符
                strIndex++;
                if (strIndex > strLength) {
                    // 表示str中已经没有字符匹配?了。
                    return false;
                }
            } else {
                if ((strIndex >= strLength) || (ch != str.charAt(strIndex))) {
                    return false;
                }
                strIndex++;
            }
        }
        return strIndex == strLength;
    }

    private Class<?> target;

    public ClassSearcher(Class<?> target) {
        this.target = target;
    }

    public List<Class<?>> search() {
        List<String> classFileList = new ArrayList<String>();
        //若为web项目则try执行成功否则为本地项目执行异常块内
        boolean isApp = false;
        try {
            String classpath = getClass().getClassLoader().getResource("").getPath();
            classpath = new File(classpath).getAbsolutePath();
            String libpath = classpath.substring(0, classpath.lastIndexOf(File.separator))
                             + File.separator + "lib";
            if (new File(libpath).exists()) {
                classFileList.addAll(findFiles(classpath, classpath, "*.class"));
                classFileList.addAll(findjarFiles(libpath, includeJars));
            } else {
                isApp = true;
            }
        } catch (Exception e) {
            isApp = true;
        }

        if (isApp) {
            String classpath = System.getProperty("java.class.path");
            String[] classpaths = null;
            if(OSinfo.isMacOS()|| OSinfo.isMacOSX()){
                classpaths = classpath.split("\\:");
            } else {
                classpaths = classpath.split("\\;");
            }
            List<String> paths = new ArrayList<String>();
            List<String> jars = new ArrayList<String>();
            for (String path : classpaths) {
                if (path.endsWith(".jar")) {
                    jars.add(path);
                } else {
                    paths.add(path);
                }
            }
            for (String path : paths) {
                classFileList.addAll(findFiles(path, path, "*.class"));
            }
            //依赖补充检测
            String lib = System.getProperty("user.dir") + File.separator + "lib";
            if (new File(lib).exists()) {
                classFileList.addAll(findjarFiles(lib, includeJars));
            }
            classFileList.addAll(findjarFiles(jars));
        }
        return extraction(target, classFileList, includePackage);
    }

    /**
     * 查找jar包中的class
     * 
     * @param baseDirName
     *            jar路径
     * @param includeJars
     * @param jarFileURL
     *            jar文件地址
     */
    private List<String> findjarFiles(String baseDirName, final List<String> includeJars) {
        List<String> classFiles = new ArrayList<String>();
        try {
            // 判断目录是否存在
            File baseDir = new File(baseDirName);
            if (!baseDir.exists() || !baseDir.isDirectory()) {
                log.error("file serach error：" + baseDirName + " is not a dir！");
            } else {
                String[] filelist = baseDir.list(new FilenameFilter() {
                    @Override
                    public boolean accept(File dir, String name) {
                        return includeJars.contains(name);
                    }
                });
                for (int i = 0; i < filelist.length; i++) {
                    JarFile localJarFile = new JarFile(new File(baseDirName + File.separator
                                                                + filelist[i]));
                    Enumeration<JarEntry> entries = localJarFile.entries();
                    while (entries.hasMoreElements()) {
                        JarEntry jarEntry = entries.nextElement();
                        String entryName = jarEntry.getName();
                        if (!jarEntry.isDirectory() && entryName.endsWith(".class")) {
                            String className = entryName.replaceAll("/", ".").substring(0,
                                entryName.length() - 6);
                            classFiles.add(className);
                        }
                    }
                    localJarFile.close();
                }
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
        return classFiles;

    }

    /**
     * 查找jar包中的class
     * 
     * @param includeJars
     */
    private List<String> findjarFiles(List<String> includeJars) {
        List<String> classFiles = new ArrayList<String>();
        try {
            for (int i = 0; i < includeJars.size(); i++) {
                JarFile localJarFile = new JarFile(new File(includeJars.get(i)));
                Enumeration<JarEntry> entries = localJarFile.entries();
                while (entries.hasMoreElements()) {
                    JarEntry jarEntry = entries.nextElement();
                    String entryName = jarEntry.getName();
                    if (!jarEntry.isDirectory() && entryName.endsWith(".class")) {
                        String className = entryName.replaceAll("/", ".").substring(0,
                            entryName.length() - 6);
                        classFiles.add(className);
                    }
                }
                localJarFile.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return classFiles;
    }

    public ClassSearcher injars(List<String> jars) {
        if (jars != null)
            includeJars.addAll(jars);
        return this;
    }

    public ClassSearcher inPackages(List<String> pack) {
        if (pack != null)
            includePackage.addAll(pack);
        return this;
    }

}
